using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;
using System.Collections;
using System.Web.SessionState;
using System.IO.Compression;
using GwtRpcNet.Rpc;

namespace GwtRpcNet.Service
{
    public class GwtRpcNetService : IHttpHandler, GwtRpcNetRemoteService, IRequiresSessionState
    {
        #region Constants

        private const string ACCEPT_ENCODING = "Accept-Encoding";
        private const string CHARSET_UTF8 = "UTF-8";
        private const string CONTENT_ENCODING = "Content-Encoding";
        private const string CONTENT_ENCODING_GZIP = "gzip";

        private const string CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
        private const string GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";

        private const int UNCOMPRESSED_BYTE_SIZE_LIMIT = 1024 * 100; //100KB //256;

        private HttpContext context;
        protected HttpContext Context
        {
            get { return context; }
        }

        #endregion

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            this.context = context;

            String requestPayload = readPayloadAsUtf8(context.Request);
            String responsePayload = processCall(requestPayload);
            writeResponse(context, responsePayload);
        }

        #region GWT Rpc

        private String readPayloadAsUtf8(HttpRequest request)
        {
            int contentLength = request.ContentLength;
            if (contentLength == -1)
            {
                // Content length must be known.
                throw new System.Exception("Content-Length must be specified");
            }

            string contentType = request.ContentType;
            bool contentTypeIsOkay = false;
            // Content-Type must be specified.
            if (contentType != null)
            {
                // The type must be plain text.
                if (contentType.StartsWith("text/plain"))
                {
                    // And it must be UTF-8 encoded (or unspecified, in which case we assume
                    // that it's either UTF-8 or ASCII).
                    if (contentType.IndexOf("charset=") == -1)
                    {
                        contentTypeIsOkay = true;
                    }
                    else if (contentType.IndexOf("charset=utf-8") != -1)
                    {
                        contentTypeIsOkay = true;
                    }
                }
            }
            if (!contentTypeIsOkay)
            {
                throw new System.Exception(
                    "Content-Type must be 'text/plain' with 'charset=utf-8' (or unspecified charset)");
            }

            Stream input = request.InputStream;
            byte[] payload = new byte[contentLength];
            input.Read(payload, 0, payload.Length);
            input.Close();

            return Encoding.UTF8.GetString(payload);
        }

        protected virtual string processCall(string payload)
        {
            RpcNetRequest rpcRequest = RpcNet.DecodeRequest(payload);
            return RpcNet.HandleRequest(this, rpcRequest);
        }

        private void writeResponse(HttpContext context, string responsePayload)
        {
            byte[] reply = Encoding.UTF8.GetBytes(responsePayload);
            String contentType = CONTENT_TYPE_TEXT_PLAIN_UTF8;

            if (acceptsGzipEncoding(context.Request)
                && shouldCompressResponse(responsePayload))
            {
                context.Response.Filter = new GZipStream(context.Response.Filter,
                         CompressionMode.Compress, true);
                context.Response.AppendHeader("Content-encoding", "gzip");


                // Compress the reply and adjust headers.
                //
                /*MemoryStream output = null;
                GZipOutputStream gzipOutputStream = null;
                Throwable caught = null;
                try
                {
                    output = new MemoryStream();
                    gzipOutputStream = new GZipOutputStream(output);
                    gzipOutputStream.Write(reply, 0, reply.Length);
                    gzipOutputStream.Finish();
                    gzipOutputStream.Flush();
                    context.Response.Clear();
                    context.Response.ClearHeaders();
                    context.Response.ClearContent();
                    context.Response.AppendHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
                    reply = output.GetBuffer();
                }
                finally
                {
                    if (null != gzipOutputStream)
                    {
                        gzipOutputStream.Close();
                    }
                    if (null != output)
                    {
                        output.Close();
                    }
                }

                if (caught != null)
                {
                    //getServletContext().log("Unable to compress response", caught);
                    //response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    return;
                }*/
            }

            // Send the reply.
            //
            context.Response.AppendHeader("Content-Length", reply.Length.ToString());
            context.Response.ContentType = contentType;
            //context.Response.Status.setStatus(HttpServletResponse.SC_OK);
            context.Response.OutputStream.Write(reply, 0, reply.Length);
            context.Response.OutputStream.Flush();
            context.Response.OutputStream.Close();
        }

        /**
        * Return true if the response object accepts Gzip encoding. This is done by
       * checking that the accept-encoding header specifies gzip as a supported
       * encoding.
       */
        private static bool acceptsGzipEncoding(HttpRequest request)
        {
            string acceptEncoding = request.Headers[ACCEPT_ENCODING];
            if (null == acceptEncoding)
                return false;

            return (acceptEncoding.IndexOf(CONTENT_ENCODING_GZIP) != -1);
        }

        protected bool shouldCompressResponse(string responsePayload)
        {
            return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
        }

        private static int estimateByteSize(string buffer) {
            return (buffer.Length * 2);
        }

        #endregion
    }
}
